#include "tentacle.h"
#include "utils.h"
#include "drawstats.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#include <utility>
#include <algorithm>

///////////////////////////////////////////////////////////////////////////////

GLuint Tentacle::s_program = 0;
GLint Tentacle::s_posAttrib;
GLint Tentacle::s_normalAttrib;
GLint Tentacle::s_stAttrib;
GLint Tentacle::s_bonesAttrib;
GLint Tentacle::s_blendsAttrib;
GLint Tentacle::s_mvmatUniform;
GLint Tentacle::s_bonesUniform;
GLint Tentacle::s_lightDiffuseUniform;
GLint Tentacle::s_lightSpecularUniform;
GLint Tentacle::s_lightPositionUniform;
GLint Tentacle::s_lightKUniform;
GLint Tentacle::s_textureSampler;

static const char s_VertexSource[] = {
#include "shader.h"
#include "tentacle.vtx.h"
};

static const char s_FragmentSource[] = {
#include "shader.h"
#include "tentacle.frg.h"
};

///////////////////////////////////////////////////////////////////////////////

Tentacle::Tentacle(uint boneCount, uint segmentCount, float radius, float height, GLuint texture)
{
  assert(boneCount >= 2);
  assert(segmentCount >= 2);

  m_texture = texture;
  m_boneCount = boneCount;
  m_segmentCount = segmentCount;
  m_radius = radius;
  m_height = height;
  createMesh();
}

Tentacle::~Tentacle()
{
  glDeleteTextures(1, &m_texture);
  glDeleteBuffers(1, &m_vertexBuffer);
  glDeleteBuffers(1, &m_indexBuffer);
}

void Tentacle::draw(uint64_t utime, const mat4 &projMat, const mat4 &camMatInv, const mat4 &modelMat)
{
  glUseProgram(s_program);
  ASSERT_GLERROR();

  glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
  ASSERT_GLERROR();

  glVertexAttribPointer(s_posAttrib, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, pos));
  glEnableVertexAttribArray(s_posAttrib);
  ASSERT_GLERROR();

  glVertexAttribPointer(s_normalAttrib, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
  glEnableVertexAttribArray(s_normalAttrib);
  ASSERT_GLERROR();

  glVertexAttribPointer(s_stAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, st));
  glEnableVertexAttribArray(s_stAttrib);
  ASSERT_GLERROR();

  glVertexAttribPointer(s_bonesAttrib, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, boneids));
  glEnableVertexAttribArray(s_bonesAttrib);
  ASSERT_GLERROR();

  glVertexAttribPointer(s_blendsAttrib, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, boneblends));
  glEnableVertexAttribArray(s_blendsAttrib);
  ASSERT_GLERROR();

  //
  vec4 lightDiffuse[4];
  vec4 lightSpecular[4];
  vec4 lightPos[4];
  lightDiffuse[0] = vec4(1, 0, 0, 1);
  lightDiffuse[1] = vec4(0, 1, 0, 1);
  lightDiffuse[2] = vec4(0, 0, 1, 1);
  lightDiffuse[3] = vec4(0.5f, 0.5f, 0, 1);
  lightSpecular[0] = vec4(1, 0.9f, 0.9f, 1);
  lightSpecular[1] = vec4(0.9f, 1, 0.9f, 1);
  lightSpecular[2] = vec4(0.9f, 0.9f, 1, 1);
  lightSpecular[3] = vec4(1, 1, 1, 1);
  lightPos[0] = camMatInv * vec4(-10, 0, 10, 1);
  lightPos[1] = camMatInv * vec4( 10, 0,-10, 1);
  lightPos[2] = camMatInv * vec4(0,-100,0,1);
  lightPos[3] = camMatInv * vec4(20,0,20,1);
  glUniform4fv(s_lightDiffuseUniform, 4, &lightDiffuse[0].x);
  glUniform4fv(s_lightSpecularUniform, 4, &lightSpecular[0].x);
  glUniform4fv(s_lightPositionUniform, 4, &lightPos[0].x);

  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, m_texture);
  glUniform1i(s_textureSampler, 0);
  ASSERT_GLERROR();

  mat4 mvmat = projMat;
  glUniformMatrix4fv(s_mvmatUniform, 1, GL_FALSE, &mvmat.m[0][0]);

  mat4 bonemats[60];
  bonemats[0] = camMatInv * modelMat;
  float ydelta = m_height/(m_boneCount-1);
  for(uint bone=1; bone<m_boneCount; bone++)
  {
    // animated skeleton:
    uint maxperiode = (uint)refitf(float(bone), 1.0f, m_boneCount-1.0f, 2000000.0f*m_boneCount, 100000.0f*m_boneCount);
    int rx = g_rand.next(maxperiode, maxperiode*2);
    //int ry = g_rand.next(maxperiode, maxperiode*2);
    int rz = g_rand.next(maxperiode, maxperiode*2);
    float maxangle = refitf(float(bone), 1, m_boneCount-1.0f, float(M_PI)/16.0f, float(M_PI)/4.0f);
    float ax = sinf((utime%rx)/float(rx)*float(M_PI)*2.0f)*maxangle;
    float ay = 0.0f; //sinf((utime%ry)/float(ry)*M_PI*2.0f)*maxangle;
    float az = sinf((utime%rz)/float(rz)*float(M_PI)*2.0f)*maxangle;
    //ax=0; ay=0; az=bone!=3?0:sinf((utime%rz)/float(rz)*M_PI*2.0f)*M_PI/2.0f;

    mat4 bonemat = mat4::makeRotateMat(ax, ay, az);
    bonemat.m[3][1] = ydelta;

    bonemats[bone] = bonemats[bone-1] * bonemat;
  }
  for(uint bone=0; bone<m_boneCount; bone++)
  {
    bonemats[bone] = bonemats[bone] * m_bones[bone].restMatInverted;
  }

  glUniformMatrix4fv(s_bonesUniform, m_boneCount, GL_FALSE, &bonemats[0].m[0][0]);
  ASSERT_GLERROR();

  glDrawElements(GL_TRIANGLES, (2*SIDES*(m_segmentCount-1))*3, GL_UNSIGNED_SHORT, 0);
  ASSERT_GLERROR();

  g_drawCalls += 1;
  g_uniformCalls += 6;
  g_triangles += (SIDES*m_segmentCount*2);

  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  ASSERT_GLERROR();

  glDisableVertexAttribArray(s_posAttrib);
  glDisableVertexAttribArray(s_normalAttrib);
  glDisableVertexAttribArray(s_stAttrib);
  glDisableVertexAttribArray(s_bonesAttrib);
  glDisableVertexAttribArray(s_blendsAttrib);
  ASSERT_GLERROR();
}

void Tentacle::compileShaders()
{
  GLuint vertexShader = compileShader(GL_VERTEX_SHADER, s_VertexSource, sizeof(s_VertexSource));
  GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, s_FragmentSource, sizeof(s_FragmentSource));
  s_program = glCreateProgram();
  glAttachShader(s_program, vertexShader);
  glAttachShader(s_program, fragmentShader);
  assert(glGetError() == 0);
  glLinkProgram(s_program);
  //printf("Shader link output:\n");
  //printInfoLog(s_program);
  assert(glGetError() == 0);
  glUseProgram(s_program);
  assert(glGetError() == 0);
  glDeleteShader(vertexShader);
  glDeleteShader(fragmentShader);

  s_posAttrib = glGetAttribLocation(s_program, "a_pos");
  s_normalAttrib = glGetAttribLocation(s_program, "a_normal");
  s_stAttrib = glGetAttribLocation(s_program, "a_st");
  s_bonesAttrib = glGetAttribLocation(s_program, "a_bones");
  s_blendsAttrib = glGetAttribLocation(s_program, "a_blends");
  s_mvmatUniform = glGetUniformLocation(s_program, "u_mvmat");
  s_bonesUniform = glGetUniformLocation(s_program, "u_bones");
  s_lightDiffuseUniform = glGetUniformLocation(s_program, "u_lightDiffuse");
  s_lightSpecularUniform = glGetUniformLocation(s_program, "u_lightSpecular");
  s_lightPositionUniform = glGetUniformLocation(s_program, "u_lightPosition");
  s_lightKUniform = glGetUniformLocation(s_program, "u_lightK");
  assert(glGetError() == 0);

  s_textureSampler = glGetUniformLocation(s_program, "s_texture");
  assert(glGetError() == 0);
}

void Tentacle::releaseShaders()
{
  glDeleteProgram(s_program);
}

void Tentacle::createMesh()
{
  m_bones[0].restMat = mat4::identity;
  for(uint bone=1; bone<m_boneCount; bone++)
  {
    mat4 restmat = mat4::identity;
    restmat.m[3][1] = m_height/(m_boneCount-1);
    m_bones[bone].restMat = m_bones[bone-1].restMat * restmat;
  }

  for(uint bone=0; bone<m_boneCount; bone++)
  {
    m_bones[bone].restMatInverted = m_bones[bone].restMat.inverted();
  }

  //Vertex vertices[(SIDES+1)*m_segmentCount];
  Vertex *vertices = (Vertex*)alloca((SIDES+1)*m_segmentCount*sizeof(Vertex));
  //memset(vertices, 0, (SIDES+1)*m_segmentCount*sizeof(Vertex));
  for(uint segment=0; segment<m_segmentCount; segment++)
  {
    vec3 segmentCenter(0, segment*m_height/(m_segmentCount-1), 0);
    float segmentRadius = m_radius; //segment==m_bones-1 ? 0.0f : m_radius; //segment*(m_radius-1)/m_bones
    for(uint side=0; side<=SIDES; side++)
    {
      float angle = side*float(M_PI)*2.0f/SIDES;
      vec3 pos(cosf(angle)*segmentRadius, 0.0f, sinf(angle)*segmentRadius);

      Vertex &v = vertices[segment*(SIDES+1)+side];
      v.pos = segmentCenter + pos;
      v.normal = pos.normalized();
      v.st.y = 0.5f * segment / m_segmentCount;
      v.st.x = side / 2.0f;

      float bi = float(segment)*(m_boneCount-1)/(m_segmentCount-1);
      float bifp = bi-int(bi);
      v.boneids[0] = int(bi+1);
      v.boneids[1] = int(bi);
      v.boneids[2] = int(bi-1);
      v.boneids[3] = 0;
      // v.boneblends.x = refitf(bifp, 0.0f, 1.0f, 0.0f, 0.5f);
      // v.boneblends.y = 0.5f;
      // v.boneblends.z = refitf(bifp, 0.0f, 1.0f, 0.5f, 0.0f);
      v.boneblends.x = powf(refitf(bifp, 0.0f, 1.0f, 0.0f, 1.0f), 1.8f)*0.5f;
      v.boneblends.y = 0.5f;
      v.boneblends.z = powf(refitf(bifp, 0.0f, 1.0f, 1.0f, 0.0f), 1.8f)*0.5f;
      v.boneblends.w = 0;

      v.boneblends = v.boneblends.normalized();

      if(v.boneids[0] >= m_boneCount) v.boneids[0] = v.boneids[1];
      if(v.boneids[2] < 0) v.boneids[2] = v.boneids[1];
      if(v.boneids[3] < 0) v.boneids[3] = v.boneids[1];
    }
  }
  glGenBuffers(1, &m_vertexBuffer);
  glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
  glBufferData(GL_ARRAY_BUFFER, (SIDES+1)*m_segmentCount*sizeof(Vertex), vertices, GL_STATIC_DRAW);
  assert(glGetError() == 0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);

  // indices
  //Triangle indices[2*SIDES*(m_segmentCount-1)];
  Triangle *indices = (Triangle*)alloca(2*SIDES*(m_segmentCount-1)*sizeof(Triangle));
  //memset(indices, 0 , sizeof(indices));
  for(uint segment=0; segment<m_segmentCount-1; segment++)
  {
    for(uint side=0; side<SIDES; side++)
    {
      unsigned short v1 = segment    *(SIDES+1) + side;
      unsigned short v2 = segment    *(SIDES+1) + (side+1);
      unsigned short v3 = (segment+1)*(SIDES+1) + side;
      unsigned short v4 = (segment+1)*(SIDES+1) + (side+1);
      Triangle &t1 = indices[segment*SIDES*2 + side*2 + 0];
      t1.v[0] = v1;
      t1.v[1] = v2;
      t1.v[2] = v4;
      Triangle &t2 = indices[segment*SIDES*2 + side*2 + 1];
      t2.v[0] = v4;
      t2.v[1] = v3;
      t2.v[2] = v1;
    }
  }

  glGenBuffers(1, &m_indexBuffer);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, 2*SIDES*(m_segmentCount-1)*sizeof(Triangle), indices, GL_STATIC_DRAW);
  assert(glGetError() == 0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}


///////////////////////////////////////////////////////////////////////////////

